﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Diagnostics;
using DevExpress.XtraEditors;
using System.Windows.Forms;
using DevExpress.XtraEditors.Controls;
using DevExpress.XtraTab;
using DevExpress.XtraBars.Docking;
using Pharmacy.Ultilities;
using DevExpress.XtraWizard;
using DevExpress.XtraNavBar;
using DevExpress.XtraGrid.Columns;
using DevExpress.XtraBars;
using DevExpress.XtraGrid;
using DevExpress.XtraGrid.Views.Base;
using DevExpress.XtraGrid.Views.Grid;
using Pharmacy.Ultilities.Localization;

namespace PharmacyPharnet.Core
{
    /// <summary>
    /// Provides methods to support for Multi-Language feature.
    /// </summary>
    /// <author>nghiemhoang</author>
    public static class LocalizerView
    {        
        /// <summary>
        /// Gets or sets event handler for SetControlPropertyEvent event.
        /// </summary>
        public static event EventHandler<SetControlPropertyEventArgs> SetControlPropertyEvent;

        public static string GetText(IView view, string key, string defaultText, params object[] args)
        {
            // --- Begin parameters checking code -----------------------------
            Debug.Assert(!string.IsNullOrEmpty(key));
            // --- End parameters checking code -------------------------------

            // check if the dictionary is initialized
            string fullKeyName = key;//minhhungle@gmail.com - 2012.08.01: dont need to combine key again. //LocalizerText.CombineKeys(view != null ? GetViewName(view) : null, key);
            
            string value;
            Dictionary<string, string> dictionary = LocalizerText.DictionaryLocalize;
            if (dictionary != null)
            {
                // current text is not inserted to language file
#if DEBUG
                if (defaultText != null && LocalizerText.CanGetText(fullKeyName) == false)
                {
                    LocalizerText.WriteTextToFile(fullKeyName, defaultText);
                }
#endif

                if (dictionary.TryGetValue(fullKeyName, out value))
                {
                    // found                
                    return args.Length == 0 ? value : string.Format(value, args);
                }
            }



            // not found or the dictionary is not yet initialized
            return defaultText != null
                       ? (args.Length == 0 ? defaultText : string.Format(defaultText, args))
                       : string.Format("$[{0}] not found.", key);
        }

        /// <summary>
        /// Applies new language to current view based on internal language dictionary.
        /// </summary>
        /// <param name="view">View will be localized. It must be a DIoms IView.</param>
        /// <author>ThangTran</author>
        /// <history>
        /// 2011.03.02 - ThangTran: added.
        /// 2011.03.21 - ThangTran: refactored code.
        /// </history>
        public static void ApplyLanguage(IView view)
        {
            // --- Begin parameters checking code -----------------------------
            Debug.Assert(view != null);
            Debug.Assert(view is Control);
            // --- End parameters checking code -------------------------------

            // ignore the case that the DialogViewBase contains single IView
            if (view.GetType().FullName == "DIoms.Core.DialogViewBase" &&
                ((Control)view).Controls.Count == 1 &&
                ((Control)view).Controls[0] is IView)
            {
                return;
            }

            // set property for current view
            SetControlProperty(view, (Control)view, "Text", (view as Control).Text);

            // both DialogViewBase and ControlViewBase have components field
            foreach (Control control in ((Control)view).Controls)
            {
                WalkIntoControl(view, control);
            }

            //Set text of tooltip of BarButtonItem when don't use 3PL
            if (!true) //ConfigManager.DIOConfig.Use3PLDesign
            {
                SetToolTipText(view);
            }
        }

        /// <summary>
        /// Walks into the given view and control, process and applies new language to it.
        /// </summary>
        /// <param name="view"></param>
        /// <param name="control">Control to generate.</param>
        /// <author>ThangTran</author>
        /// <history>
        /// 2011.03.02 - ThangTran: added.
        /// 2011.03.21 - ThangTran: refactored code.
        /// 2011.04.19 - ThangTran: fixed issue when the GridColumn is localized 2 times.
        /// 2011.05.23 - ThangTran: added code to process controls' tooltip.
        /// 2011.05.24 - ThangTran: added code to process inner editor of gridview column, RadioGroupItem, issue #1846.
        /// </history>
        private static void WalkIntoControl(IView view, Control control)
        {
            // --- Begin parameters checking code -----------------------------
            Debug.Assert(view != null);
            Debug.Assert(control != null);
            // --- End parameters checking code -------------------------------

            // do not walk into the control that is an IView
            // its localization is already taken care by another ApplyLanguage() call
            if (control is IView)
            {
                return;
            }

            // define a list of standard controls that can get the Text from
            List<Type> standardControls = new List<Type>();

            // === standard WinForm controls ==================================
            standardControls.AddRange(new Type[]
                                          {
                                              typeof (Label),
                                              typeof (GroupBox),
                                          });

            // === DevExpress controls ==================================;
            standardControls.AddRange(new Type[]
                                          {
                                              typeof (LabelControl),
                                              typeof (SimpleButton),
                                              typeof (PanelControl),
                                              typeof (GroupControl),
                                              typeof (CheckEdit),
                                              typeof (RadioGroup),
                                              typeof (RadioGroupItem),
                                              typeof (XtraTabPage),
                                              typeof (DockPanel),
                                              typeof (WelcomeWizardPage),
                                              typeof (WizardPage),
                                              typeof (WizardControl)
                                          });

            // process properties from standard controls
            if (standardControls.Contains(control.GetType()))
            {
                SetControlProperty(view, control, "Text", control.Text);
            }

            //// process BaseEdit Null text property
            //if (control is BaseEdit)
            //{
            //    if ((control as BaseEdit).Properties != null)
            //    {                                        
            //        SetControlProperty(view, control, "Properties.NullText", (control as BaseEdit).Properties.NullText);
            //    }
            //}

            // process RadioGroup
            if (control is RadioGroup)
            {
                LinqToObject
                    .From<RadioGroupItem>((control as RadioGroup).Properties.Items)
                    .ForEach(delegate(RadioGroupItem t) { SetControlProperty(view, t, "Value", t.Value.ToString()); });
            }

            // process Wizard control
            if (control is WizardControl)
            {                
                SetControlProperty(view, control, "CancelText", (control as WizardControl).CancelText);
                SetControlProperty(view, control, "FinishText", (control as WizardControl).FinishText);
                SetControlProperty(view, control, "HelpText", (control as WizardControl).HelpText);
                SetControlProperty(view, control, "NextText", (control as WizardControl).NextText);
                SetControlProperty(view, control, "PreviousText", (control as WizardControl).PreviousText);
            }

            // process Wizard Page
            if (control is WizardPage)
            {
                SetControlProperty(view, control, "DescriptionText", (control as WizardPage).DescriptionText);
            }

            // process Wizard Welcome Page
            if (control is WelcomeWizardPage)
            {
                SetControlProperty(view, control, "IntroductionText", (control as WelcomeWizardPage).IntroductionText);
                SetControlProperty(view, control, "ProceedText", (control as WelcomeWizardPage).ProceedText);
            }

            // process NavBarControl
            if (control is NavBarControl)
            {
                foreach (NavBarGroup group in (control as NavBarControl).Groups)
                {
                    SetControlProperty(view, group, "Caption", group.Caption);
                }

                foreach (NavBarItem item in (control as NavBarControl).Items)
                {
                    SetControlProperty(view, item, "Caption", item.Caption);
                }
            }

            // process gridview
            if (control is GridControl)
            {
                LinqToObject
                    .From<BaseView>((control as GridControl).ViewCollection)
                    .OfType<GridView>()
                    .SelectMany<GridColumn>(delegate(GridView t) { return t.Columns; })
                    .Distinct()
                    .ForEach(delegate(GridColumn t)
                                 {
                                     // set text of the "caption" property
                                     SetControlProperty(view, t, "caption", t.Caption);

                                     // move to the inner control of the column, if any
                                     if (t.ColumnEdit != null && t.ColumnEdit.OwnerEdit != null)
                                     {
                                         WalkIntoControl(view, t.ColumnEdit.OwnerEdit);
                                     }
                                 });
            }

            // process tooltip
            if (control is BaseControl)
            {
                SetControlProperty(view, control, "ToolTip" ,(control as BaseControl).ToolTip);
                SetControlProperty(view, control, "ToolTipTitle", (control as BaseControl).ToolTipTitle);
            }

            foreach (Control subControl in control.Controls)
            {
                WalkIntoControl(view, subControl);
            }
        }

        /// <summary>
        /// Sets tooltip text.
        /// </summary>
        /// <param name="view"></param>
        /// <author>NghiemHoang</author>
        /// <history>
        /// 2011.04.15 - NghiemHoang: added.
        /// 2011.04.15 - ThangTran: added xml-doc, refactored code, changed usage ReflectionHelper.SetObjectValue() to SetControlProperty().
        /// </history>
        public static void SetToolTipText(IView view)
        {
            // get ALL fields declared in this object
            BarButtonItem[] barItems = LinqToObject
                .From(view.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
                // where the item is DevExpress Bar Manager
                .Where(delegate(FieldInfo t) { return t.FieldType == typeof(BarManager); })
                // get value of bar manager objects
                .Select<BarManager>(delegate(FieldInfo t) { return (BarManager)t.GetValue(view); })
                // get bar in bar mananger
                .SelectMany<Bar>(delegate(BarManager t) { return t.Bars; })
                // select visible bars only
                .Where(delegate(Bar t) { return t.Visible; })
                //// all returned item must be not null
                //.DebugAssert(delegate(Bar t) { return t != null; })
                //// cast to bar
                //.Cast<Bar>()
                // get all their children                
                .SelectMany<LinkPersistInfo>(delegate(Bar t) { return t.LinksPersistInfo; })
                // select .Item property of LinksPersistInfo
                .Select<BarItem>(delegate(LinkPersistInfo t) { return t.Item; })
                // t.Item should not be null
                .DebugAssert(delegate(BarItem t) { return t != null; })
                // only get BarButtonItem
                .OfType<BarButtonItem>()
                .ToArray();

            if (barItems.Length == 0)
            {
                return;
            }

            foreach (BarButtonItem bar in barItems)
            {
                // ignore invisible bar item
                if (bar.Visibility != BarItemVisibility.Always)
                {
                    continue;
                }

                // skip if there is nothing to process
                if (bar.SuperTip == null || bar.SuperTip.Items.Count <= 0)
                {
                    continue;
                }

                foreach (object tip in bar.SuperTip.Items)
                {
                    // make sure the object has member named Text
                    Debug.Assert(ReflectionHelper.HasMember(tip, "Text"));
                    string keyName = LocalizerText.CombineKeys(GetKeyName(view, bar), tip.GetType().Name);
                    SetControlProperty(view, tip, keyName, "Text");                        
                }
            }
        }

        /// <summary>
        /// Sets display text of control corresponding with value of Dictionary object
        /// </summary>
        /// <param name="view">Reference to the view.</param>
        /// <param name="control">Reference to the control.</param>
        /// <param name="propertyPath">Property path that can be be single member name or multiple member names, seperated by dots.</param>
        /// <author>ThangTran</author>
        /// <history>
        /// 2011.03.02 - ThangTran: added.
        /// 2011.03.21 - ThangTran: refactored code.
        /// </history>
        private static void SetControlProperty(IView view, object control, string propertyPath, string defaultText)
        {
            SetControlProperty(view, control, null, propertyPath, defaultText);
        }

        /// <summary>
        /// Sets display text of control corresponding with value of Dictionary object
        /// </summary>
        /// <param name="view">Reference to the view.</param>
        /// <param name="control">Reference to the control.</param>
        /// <param name="customKey">Custom key of the control property. This is an optional parameter.</param>
        /// <param name="propertyPath">Property path that can be be single member name or multiple member names, seperated by dots.</param>
        /// <author>ThangTran</author>
        /// <history>
        /// 2011.03.02 - ThangTran: added.
        /// 2011.03.21 - ThangTran: refactored code.
        /// 2011.04.18 - ThangTran: added overload to add customKey parameter.
        /// 2011.05.23 - ThangTran: added code to ignore null value.
        /// </history>
        private static void SetControlProperty(IView view, object control, string customKey, string propertyPath, string defaultText)
        {
            // --- Begin parameters checking code -----------------------------
            Debug.Assert(control != null);
            Debug.Assert(!string.IsNullOrEmpty(propertyPath));
            Debug.Assert(ReflectionHelper.HasMember(control, propertyPath));
            Debug.Assert(ReflectionHelper.GetObjectValue<object>(control, propertyPath) is string);
            // --- End parameters checking code -------------------------------

            // ignore if the value is null or empty
            if (string.IsNullOrEmpty(ReflectionHelper.GetObjectValue<string>(control, propertyPath)))
            {
                return;
            }

            // calculate key name by using object information
            string keyName = customKey ?? GetKeyName(view, control);
            keyName = LocalizerText.CombineKeys(keyName, propertyPath);            

            // set the property of the given control
            //string value = CanGetText(keyName) ? GetText(keyName, null) : GetText(keyName, null);
            //string value = GetText(keyName, ReflectionHelper.GetObjectValue<string>(control, propertyPath));
            string value = GetText(view, keyName, defaultText);            

            // invoke event, if any
            if (SetControlPropertyEvent != null)
            {
                // prepare event args
                SetControlPropertyEventArgs args = new SetControlPropertyEventArgs();
                args.View = view;
                args.Control = control;
                args.PropertyPath = propertyPath;
                args.Key = keyName;
                args.OldValue = ReflectionHelper.GetObjectValue<string>(control, propertyPath);
                args.NewValue = value;

                // invoke the event handler
                SetControlPropertyEvent(view, args);

                // apply changes from event handler
                if (args.IsCancel)
                {
                    return;
                }
                if (args.IsNewValueOverriden)
                {
                    value = args.NewValue;
                }
            }

            ReflectionHelper.SetObjectValue(control, propertyPath, value);
        }

        /// <summary>
        /// Gets the key name of given control and its parent view.
        /// </summary>
        /// <param name="view">Reference to the view.</param>
        /// <param name="control">Reference to the control.</param>
        /// <author>ThangTran</author>
        /// <history>
        /// 2011.03.02 - ThangTran: added.
        /// 2011.03.21 - ThangTran: refactored code.
        /// </history>
        /// <returns></returns>
        private static string GetKeyName(IView view, object control)
        {
            // --- Begin parameters checking code -----------------------------
            Debug.Assert(control != null);
            // --- End parameters checking code -------------------------------

            // start at current object
            object currentObject = control;

            // prepare key
            string key = "";
            string viewName = GetViewName(view);
            Debug.Assert(!string.IsNullOrEmpty(viewName));

            while (true)
            {
                // check if the current object is the parent
                if (currentObject is IView)
                {
                    viewName = GetViewName(currentObject as IView);
                    return LocalizerText.CombineKeys(viewName, key);
                }

                // try to get key of the Name property
                if (ReflectionHelper.HasMember(currentObject, "Name"))
                {
                    // append to the prefix [object.Name].[processed keys]
                    key = LocalizerText.CombineKeys(ReflectionHelper.GetObjectValue<string>(currentObject, "Name"), key);
                    if (currentObject is GridColumn)
                    {
                        // the tree stops here, no parent can be detected
                        // append the form where the grid is declared
                        key = LocalizerText.CombineKeys(viewName, key);
                        return key;
                    }

                    // check if the object follows Parent-Children pattern
                    if (ReflectionHelper.HasMember(currentObject, "Parent"))
                    {
                        // move upper
                        currentObject = ReflectionHelper.GetObjectValue<object>(currentObject, "Parent");
                        continue;
                    }
                }

                // assume the prefix is not view.FullName
                Debug.Assert(key != null);
                Debug.Assert(view != null);
                //viewName = view.GetType().FullName;
                //Debug.Assert(!string.IsNullOrEmpty(viewName));
                Debug.Assert(!key.StartsWith(viewName));
                return LocalizerText.CombineKeys(viewName, key);
            }
        }

        /// <summary>
        /// Gets view's name. Support Generic types.
        /// </summary>
        /// <param name="view"></param>
        /// <returns></returns>
        private static string GetViewName(IView view)
        {
            // --- Begin parameters checking code -----------------------------
            Debug.Assert(view != null);
            // --- End parameters checking code -----------------------------
            string parentName = string.Empty;

            if (view is Form)
            {
                return view.GetType().FullName;
            }
            else
            {
                if (view is ControlViewBase)
                {
                    Form containerForm = ((ControlViewBase)view).FindForm();
                    parentName = containerForm == null ? string.Empty : containerForm.GetType().FullName;
                }
            }

            string viewName;
            Type viewType = view.GetType();
            if (viewType.IsGenericType)
            {
                string baseTypeName = viewType.Name.Replace("`1", "");
                string genericTypeName = string.Empty;
                foreach (Type typeArgument in viewType.GetGenericArguments())
                {
                    genericTypeName += string.Format(".{0}", typeArgument.Name);
                }
                viewName = string.Format("{0}{1}", baseTypeName, genericTypeName);
            }
            else
            {
                viewName = view.GetType().FullName;
            }

            if (!string.IsNullOrEmpty(parentName)) viewName = string.Format("{0}.{1}", parentName, viewName);

            return viewName;
        }

        #region Nested classes

        /// <summary>
        /// Provides data for BeforeSetControlProperty and AfterSetControlProperty event.
        /// </summary>
        public class SetControlPropertyEventArgs : EventArgs
        {
            public IView View;
            public object Control;
            public string PropertyPath;
            public string Key;
            public string OldValue;
            public string NewValue;
            public bool IsCancel;
            public bool IsNewValueOverriden;
        }        

        #endregion        
    }
}
